#ifndef JSIEXECUTOR_H
#define JSIEXECUTOR_H
#include <jsi/jsi.h>

#include <unordered_map>

#include "JSINativeModules.h"
#include "ModuleRegistry.h"

namespace jsiexecutor {
using namespace facebook::jsi;

using Logger = std::function<void(const std::string& message)>;
class JSIExecutor {
 public:
  JSIExecutor(std::shared_ptr<Runtime> runtime, std::shared_ptr<ModuleRegistry> moduleRegistry);
  // init runtime context global objs for native modules
  void initializeRuntime();
  // execute js script or bytecode
  void loadBundle(const std::shared_ptr<const Buffer>& buffer, const std::string& sourceUrl);

  // call a function that is in expressions object by name
  template <typename... Args>
  Value callFunction(const std::string& functionName, Args&&... args) {
    return callFunction(functionName, {detail::toValue(*runtime_, std::forward<Args>(args))...});
  }
  Value callObjFunction(const std::string& objName, const std::string& functionName, const std::vector<Value>& args);
  Value callFunctionVec(const std::string& functionName, const std::vector<Value>& args);

  template <typename... Args>
  Value callFunction(int expressionIdx, Args&&... args) {
    return callFunction(expressionIdx, {detail::toValue(*runtime_, std::forward<Args>(args))...});
  }
  // call a function that is in globalThis object by index
  Value callFunctionVec(int expressionIdx, const std::vector<Value>& args);

  // get a function object from expressions object by index
  Value getFunction(int expressionIdx);

  // set 'name' property to globalThis object
  void setGlobalVariable(const std::string& name, const std::string& json);
  // create a new object and set it to globalThis object
  void setGlobalVariable(const std::string name, const std::string& propertyName, const std::string& json);

  void setGlobalHostFunc(const std::string& functionName, HostFunctionType funcType, unsigned int paramCount);

  // call native module func by module name & method name, should be private but make it public for test only
  Value callNativeModule(const std::string& module, const std::string& method, const Array args);

  // invoke callback by callbackId
  void invokeCallback(unsigned int callbackId);

  std::shared_ptr<Runtime> getRuntime() { return runtime_; }
  Object getGlobalThis();

  ~JSIExecutor() {
    if (nativeModules_) {
      nativeModules_->reset();
    }
  }

  void bindNativeLogger(Logger logger);

 private:
  // call a function that is in globalThis object by name
  Value callFunction(const std::string& functionName, const std::initializer_list<Value>& args);

  // call a function that is in expressions object by index
  Value callFunction(int expressionIdx, const std::initializer_list<Value>& args);

  class NativeModuleProxy;

  std::shared_ptr<Runtime> runtime_;
  Object globalObj_;
  std::shared_ptr<ModuleRegistry> moduleRegistry_;
  std::shared_ptr<JSINativeModules> nativeModules_;
  std::unordered_map<size_t, Function> callbacks_;
  std::unordered_map<size_t, Array> callbackParams_;
  unsigned int callbackId_;

  const char* EXPRESSION = "$expression";
};
}  // namespace jsiexecutor
#endif  // JSIEXECUTOR_H
